Skip to content

Method: isDeclaredInClass(FieldSpecification, Class)

1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.model.metamodel;
19:
20: import cz.cvut.kbss.jopa.exception.StaticMetamodelInitializationException;
21: import org.slf4j.Logger;
22: import org.slf4j.LoggerFactory;
23:
24: import java.lang.reflect.Field;
25: import java.lang.reflect.Modifier;
26: import java.util.Objects;
27: import java.util.Optional;
28: import java.util.Set;
29:
30: /**
31: * Initializes static metamodel based on the provided runtime metamodel.
32: * <p>
33: * Static metamodel initialization involves going through all managed types in the metamodel, finding a corresponding
34: * static metamodel class (if exists) and populating its attributes with values from the actual runtime metamodel.
35: */
36: public class StaticMetamodelInitializer {
37:
38: private static final Logger LOG = LoggerFactory.getLogger(StaticMetamodelInitializer.class);
39:
40: /**
41: * Suffix appended to a static metamodel class corresponding to a metamodel class.
42: */
43: private static final String STATIC_METAMODEL_CLASS_SUFFIX = "_";
44:
45: private final Metamodel metamodel;
46:
47: public StaticMetamodelInitializer(Metamodel metamodel) {
48: this.metamodel = metamodel;
49: }
50:
51: /**
52: * Executes the static metamodel initialization.
53: */
54: public void initializeStaticMetamodel() {
55: LOG.debug("Initializing static metamodel.");
56: processEntities();
57: processManagedTypes();
58: }
59:
60: private void processEntities() {
61: metamodel.getEntities().forEach(this::processType);
62: }
63:
64: private void processType(ManagedType<?> mt) {
65: final Optional<Class<?>> smClass = tryFindingClass(mt);
66: if (smClass.isEmpty()) {
67: LOG.trace("No static metamodel type found for {}.", mt);
68: return;
69: }
70: LOG.debug("Processing static metamodel class {} corresponding to {}.", smClass.get(), mt);
71: verifyParents(smClass.get(), mt);
72: try {
73: initStaticMembers(mt, smClass.get());
74: } catch (IllegalAccessException e) {
75: throw new StaticMetamodelInitializationException("Unable to initialize static metamodel class " + smClass,
76: e);
77: }
78: }
79:
80: private static Optional<Class<?>> tryFindingClass(ManagedType<?> type) {
81: final String staticName = type.getJavaType().getName() + STATIC_METAMODEL_CLASS_SUFFIX;
82: try {
83: final Class<?> smClass = Class.forName(staticName);
84: if (isNotStaticMetamodelForType(smClass, type.getJavaType())) {
85: return Optional.empty();
86: }
87: return Optional.of(smClass);
88: } catch (ClassNotFoundException e) {
89: // Swallow the exception, this just means there is no static metamodel type for the specified entity
90: return Optional.empty();
91: }
92: }
93:
94: private static boolean isNotStaticMetamodelForType(Class<?> smClass, Class<?> metamodelClass) {
95: return smClass.getAnnotation(StaticMetamodel.class) == null ||
96: !smClass.getAnnotation(StaticMetamodel.class).value().equals(metamodelClass);
97: }
98:
99: private static void verifyParents(Class<?> smClass, ManagedType<?> type) {
100: if (type instanceof IdentifiableType<?> idType && !idType.getSupertypes().isEmpty()) {
101: for (IdentifiableType<?> superType : idType.getSupertypes()) {
102: final Optional<Class<?>> supertypeSm = tryFindingClass(superType);
103: if (supertypeSm.isEmpty() || !Objects.equals(smClass.getSuperclass(), supertypeSm.get())) {
104: throw new StaticMetamodelInitializationException("Managed type " + type +
105: " has a managed supertype. A corresponding relationship must exist between static metamodel classes.");
106: }
107: }
108: }
109: }
110:
111: private <T> void initStaticMembers(ManagedType<T> et, Class<?> smClass) throws IllegalAccessException {
112: final Field[] fields = smClass.getDeclaredFields();
113: for (Field f : fields) {
114: if (!isCanonicalMetamodelField(f)) {
115: LOG.debug("Skipping field {}, it is not canonical (public static).", f);
116: continue;
117: }
118: final FieldSpecification<T, ?> att = getMetamodelMember(f, et);
119: setFieldValue(f, att);
120: }
121: }
122:
123: private static boolean isCanonicalMetamodelField(Field field) {
124: return Modifier.isStatic(field.getModifiers()) && Modifier.isPublic(field.getModifiers());
125: }
126:
127: private static void setFieldValue(Field field, Object value) throws IllegalAccessException {
128: assert isCanonicalMetamodelField(field);
129: field.set(null, value);
130: }
131:
132: private <T> FieldSpecification<T, ?> getMetamodelMember(Field field, ManagedType<T> type) {
133: LOG.trace("Finding metamodel member for static metamodel field {}.", field);
134: return getDeclaredIdentifier(field, type)
135: .orElseGet(() -> getDeclaredAttribute(field, type)
136: .orElseGet(() -> getDeclaredTypes(field, type)
137: .orElseGet(() -> getDeclaredProperties(field, type)
138: .orElseThrow(() -> new StaticMetamodelInitializationException(
139: "No corresponding metamodel member found for static metamodel field " +
140: field)))));
141: }
142:
143: private static <T> Optional<FieldSpecification<T, ?>> getDeclaredIdentifier(Field field, ManagedType<T> type) {
144: if (!(type instanceof IdentifiableType<T> mt)) {
145: return Optional.empty();
146: }
147: return Objects.equals(field.getName(), mt.getIdentifier().getJavaField().getName()) &&
148: isDeclaredInClass(mt.getIdentifier(), type.getJavaType()) ?
149: Optional.of((Identifier<T, ?>) mt.getIdentifier()) : Optional.empty();
150: }
151:
152: private static boolean isDeclaredInClass(FieldSpecification<?, ?> fs, Class<?> cls) {
153: return fs.getJavaField().getDeclaringClass().equals(cls);
154: }
155:
156: private static <T> Optional<FieldSpecification<T, ?>> getDeclaredAttribute(Field field, ManagedType<T> type) {
157: try {
158: return Optional.of(type.getDeclaredAttribute(field.getName()));
159: } catch (IllegalArgumentException e) {
160: return Optional.empty();
161: }
162: }
163:
164: private static <T> Optional<FieldSpecification<T, ?>> getDeclaredTypes(Field field, ManagedType<T> type) {
165: return type.getTypes() != null &&
166: Objects.equals(field.getName(), type.getTypes().getJavaField().getName()) &&
167: isDeclaredInClass(type.getTypes(), type.getJavaType()) ?
168: Optional.of((TypesSpecification<T, ?>) type.getTypes()) : Optional.empty();
169: }
170:
171: private static <T> Optional<FieldSpecification<T, ?>> getDeclaredProperties(Field field, ManagedType<T> type) {
172: return type.getProperties() != null &&
173: Objects.equals(field.getName(), type.getProperties().getJavaField().getName()) &&
174: isDeclaredInClass(type.getProperties(), type.getJavaType()) ?
175: Optional.of((PropertiesSpecification<T, ?, ?, ?>) type.getProperties()) : Optional.empty();
176: }
177:
178: private void processManagedTypes() {
179: final Set<ManagedType<?>> managedTypes = metamodel.getManagedTypes();
180: managedTypes.removeAll(metamodel.getEntities());
181: managedTypes.forEach(this::processType);
182: }
183: }